# -*- coding: utf-8 -*-
"""
#-------------------------------------------------------------------------------
# Nom:         analyseChroniques
#
# Auteur:      Alain Gauthier
#
# Créé le:     19/08/2013
# Copyright:   (c) eaucéa 2014
# Licence:     GNU Public Licence (GPL)

/***************************************************************************
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/

Lecture de données brutes venant de fichiers CSV et représentant des chroniques
Analyse des chroniques et traitements liés aux changement d'unités mm <--> m3/s

#-------------------------------------------------------------------------------
"""

# modules système
import os
import csv
import datetime
# modules externes
import numpy as np

# valeur représentant l'absence de donnée
__NO_VALUE__=-9999
# valeur de la première ligne de mesure des débits
__DEPART_QMES__=8

# TODO : construire une classe représentant une chronique (Pluie, Evapo, Débit)
#-------------------------------------------------------------------------------

def lireFichierChronique(aNomFichier):
    """
    lecture d'un fichier de chroniques de données
    renvoie une matrice contenant les données brutes à interpréter
    """
    matrice = []
    fid = open (aNomFichier, 'r')
    try:
        # format lu
        formatcsv = csv.Sniffer().sniff(fid.read(5))
        fid.seek(0)
        # structure csv
        reader = csv.reader (fid, delimiter=';')

        # champs du fichier lu
        champs = next(reader)
        # lecture des données brutes
        for row in reader:
            try:
                matrice.append(row)
            except:
                raise IOError("erreur de lecture dans le fichier de chronique : " + aNomFichier)

    finally:
        # fermeture fichier
        fid.close()
        return matrice

#-------------------------------------------------------------------------------

def construire (AbvStation, AcheminChroniques, Adt):
    """
    Chargement de la chronique contenue dans AcheminChroniques et correspondant au bassin
    versant identifié par AbvStation.
    Le pas de temps de la chronique dans le fichier doit être cohérent avec Adt
    Renvoie un dictionnaire resultat où :
        resultat['temps'] contient une liste des dates de mesure au pas de temps Adt
        resultat['valeur'] contient une liste des valeurs mesurées
    """
    # résultat
    resultat = dict()

    # lecture du fichier csv
    nomfichier = os.path.join(AcheminChroniques,AbvStation+'.csv')
    matrice = lireFichierChronique(nomfichier)

    # interprétation des données
    # pour manipuler la matrice
    tableau = np.array(matrice)
    resultat['temps']= [decoder_date(elt) for elt in tableau[:,0]]
    resultat['valeur']= [convertir_str(elt) for elt in tableau[:,1]]

    # résultat
    return resultat

#-------------------------------------------------------------------------------

def chargerChroniqueQ(AidStation, AfichierQmesure):
    """
    Chargement de la chronique de débits mesurés à partir du fichier csv AfichierQmesure
    Renvoie le dictionnaire resultat où :
        resultat['temps'] contient une liste des dates de mesure au pas de temps Adt
        resultat['valeur'] contient une liste des valeurs mesurées
    """
    # résultat
    resultat = dict()

    # lecture du fichier csv
    matrice = lireFichierChronique(AfichierQmesure)

    # interprétation des données
    # l'identifiant de la station se trouve à la 2ème ligne du fichier, c'est à dire la 1ère de 'matrice'
    # pour manipuler la matrice
    tableau = np.array(matrice)
    liste_idStation = tableau[0,:]
    # le(s) indice(s) de la station est(sont) renvoyé(s) dans une liste de liste d'indices
    idcol = np.argwhere(liste_idStation==AidStation)[0][0]

    resultat['temps']= [decoder_date(elt) for elt in tableau[__DEPART_QMES__:,0]]
    resultat['valeur']= [convertir_str(elt) for elt in tableau[__DEPART_QMES__:,idcol]]

    # résultat
    return resultat

#-------------------------------------------------------------------------------

def traiterDonneesGLDAS(AchroniquesEvapo, AdtEvapo, AlistedtPluie):
    """
    traitement spécifique des données GLDAS de la chronique Evapotranspiration :
    les valeurs de AchroniquesEvapo sont des moyennes sur des durées de 3h et
    la chronique est construite sur les dates de AlistedtPluie
    Renvoie un dictionnaire resultat où :
        resultat['temps'] contient la même liste que AlistedtPluie
        resultat['valeur'] contient les valeurs calculées à partir de la mesure AchroniquesEvapo
    IMPORTANT : le pas de temps est donné en jours !
    """
    # résultat
    resultat = dict()

    # construction de la chronique au pas de temps dtPluie
    resultat['temps'] = AlistedtPluie[:]
    resultat['valeur'] = []
    # tableau des dates de données brutes d'evapotranspiration
    tabTpsEvapo = AchroniquesEvapo['temps']
    # tableau des dates de pluviométrie
    tabTpsPluie = AlistedtPluie
    # recherche des valeurs correspondant aux éléments de tabTpsPluie
    tabValeurGLDAS=AchroniquesEvapo['valeur']
    for tps in tabTpsPluie[:-1]:
        indexinf = recherche_inf(tps,tabTpsEvapo)
        if indexinf>=len(tabTpsPluie):
            continue
        # pas de temps de la donnée de pluie
        dtPluie = tabTpsPluie[indexinf+1]-tabTpsPluie[indexinf]
        resultat['valeur'].append(tabValeurGLDAS[indexinf]*dtPluie.total_seconds())

    # resultat
    return resultat


#-------------------------------------------------------------------------------

def recherche_inf(Acible,Atableau):
    """
    renvoie l'index i de Atableau tel que:
        Atableau[i+1]<Acible<=Atableau[i]
    IMPORTANT : Atableau est ordonné par ordre croissant
    """
    ires=0
    while Atableau[ires]<Acible:
        ires=ires+1
    return ires

#-------------------------------------------------------------------------------

def convertir_str(astr):
    """
    convertion d'une chaîne en float, renvoie __NO_VALUE__ si la chaîne n'est pas convertible
    """
    try:
        return float(astr)
    except:
        return __NO_VALUE__

#-------------------------------------------------------------------------------

def decoder_date(astr):
    """
    décodage de la date contenue dans astr à l'un des formats suivants :
        yyyy-mm-dd
        dd/mm/yyyy
        dd/mm/yyyy hh:mm
    renvoie un objet datetime.date
    """
    listedate=astr.split('-')
    if len(listedate)>1:
        resdate=datetime.date(int(listedate[0]),int(listedate[1]),int(listedate[2]))
    listedate=astr.split('/')
    if len(listedate)>1:
        resdate=datetime.date(int(listedate[2][:4]),int(listedate[1]),int(listedate[0]))
    return resdate

#-------------------------------------------------------------------------------

def debitEn_mm(AchroniquesQm3s, Adt, AaireBV):
    """
    Calcule une chronique représentant la transformée des valeurs de AchroniquesQm3s en mm sur le pas de temps
    Adt passé en paramètre (en jours). AchroniqueQm3s contient des valeurs en m³/s.AaireBV est donné en km²
    CAS PARTICULIER : si Adt = 31 jours alors le calcul est fait sur la base du pas de temps en mois calendaires !
    Renvoie le dictionnaire resultat où :
        resultat['temps'] contient une liste des dates au pas de temps Adt
        resultat['valeur'] contient une liste des valeurs mesurées
    """
    # initialisation
    resultat=dict()
    resultat['temps'] = []
    resultat['valeur'] = []

    # construction par itérations
    tabDates = AchroniquesQm3s['temps'][:]
    # HYPOTHESE IMPORTANTE : date_courante est le PREMIER jour d'un mois (la chronique commence en début de mois)
    date_courante = tabDates[0]
    dtChronique = tabDates[1]-date_courante
    # ajout de la date fin pour prendre en compte toute les données
    if Adt.days == 31:
        dfin=tabDates[-1]+Adt
        tabDates.append(datetime.date(dfin.year,dfin.month,1))
    else:
        tabDates.append(tabDates[-1]+dtChronique)
    # date suivante de la chronique résultat
    date_suivante = date_courante+Adt
    if Adt.days == 31:
        # pas de temps mensuel calendaire
        date_suivante = datetime.date(date_suivante.year,date_suivante.month,1)

    # boucle de construction
    index_courant = 0
    while date_suivante<=tabDates[-1]:
        # date résultat
        resultat['temps'].append(date_courante)
        # valeur résultat
        valeur=0
        while tabDates[index_courant]<date_suivante:
            valeur = valeur+AchroniquesQm3s['valeur'][index_courant]*dtChronique.total_seconds()/(float(AaireBV)*1000)
            index_courant = index_courant+1
        # résultat moyen sur le nombre de jours du mois
        nbjours=date_suivante-date_courante
        resultat['valeur'].append(valeur)
        # pas de temps suivant
        date_courante=date_suivante
        date_suivante=date_suivante+Adt
        if Adt.days == 31:
            # pas de temps mensuel calendaire
            date_suivante = datetime.date(date_suivante.year,date_suivante.month,1)

    # résultat
    return resultat

#-------------------------------------------------------------------------------

def debit_enM3S(AQmm,Atps,AaireBV):
    """
    CETTE FONCTION N'EST PAS la fonction réciproque de 'debitEn_mm'
    Calcul de la chronique des débits correspondant à AQmm en m3/s pour un BV d'aire AaireBV et sur
    la chronique de temps Atps
    Renvoie la chronique des débits.
    Attention : les débits seuls sont renvoyés et non la chronique
                complète sous forme dico['valeur'] et dico['temps']
    """
    # résultat en m3/s : copie des valeurs avant calcul
    resultat=np.array(AQmm)
    # pas de temps selon les valeurs de Atps
    for i in range(len(resultat)):
        if i<(len(Atps)-1):
            dt=Atps[i+1]-Atps[i]
        else:
            dt=Atps[-1]-Atps[-2] # le dernier pas de temps est répété si l'info manque
        # calcul de conversion
        resultat[i]=resultat[i]*AaireBV*1000.0/dt.total_seconds()
    # fin
    return resultat

#-------------------------------------------------------------------------------

def alignerChroniques(Achron1,Achron2,Achron3):
    """
    Aligne le début de la chronique sur celle commençant à la dernière date
    HYPOTHESE : les chroniques sont au même pas de temps
    Renvoie les chroniques alignées resultat1,resultat2,resultat3 respectivement de
    Achron1,Achron2,Achron3
    """
    # resultat
    res1 = dict()
    res2 = dict()
    res3 = dict()

    # analyse des départs
    tabt1 = Achron1['temps']
    tini1 = tabt1[0]
    tabt2 = Achron2['temps']
    tini2 = tabt2[0]
    tabt3 = Achron3['temps']
    tini3 = tabt3[0]
    tabini=np.array([tini1,tini2,tini3])
    tmax=np.max(tabini)

    # alignement des chroniques
    if tmax==tini1:
        res1['temps']=Achron1['temps'][:]
        res1['valeur']=Achron1['valeur'][:]
        idini2=tabt2.index(tmax)
        idini3=tabt3.index(tmax)
        res2['temps']=Achron2['temps'][idini2:]
        res2['valeur']=Achron2['valeur'][idini2:]
        res3['temps']=Achron3['temps'][idini3:]
        res3['valeur']=Achron3['valeur'][idini3:]
    elif tmax==tini2:
        res2['temps']=Achron2['temps'][:]
        res2['valeur']=Achron2['valeur'][:]
        idini1=tabt1.index(tmax)
        idini3=tabt3.index(tmax)
        res1['temps']=Achron1['temps'][idini1:]
        res1['valeur']=Achron1['valeur'][idini1:]
        res3['temps']=Achron3['temps'][idini3:]
        res3['valeur']=Achron3['valeur'][idini3:]
    elif tmax==tini3:
        res3['temps']=Achron3['temps'][:]
        res3['valeur']=Achron3['valeur'][:]
        idini1=tabt1.index(tmax)
        idini2=tabt2.index(tmax)
        res1['temps']=Achron1['temps'][idini1:]
        res1['valeur']=Achron1['valeur'][idini1:]
        res2['temps']=Achron2['temps'][idini2:]
        res2['valeur']=Achron2['valeur'][idini2:]

    # renvoie le résultat
    return res1,res2,res3

#-------------------------------------------------------------------------------
#-------------------------------------------------------------------------------
